#pragma once
#include <vector>
#include "material.h"
#include "CG_skel_w_MFC.h"
#include "vec.h"
#include "mat.h"
#include "GL/glew.h"
#include "Lights.h"
#include "FastArray.h"
//#define USE_WIREFRAME

typedef enum {SHAD_NONE, SHAD_FLAT, SHAD_GUE, SHAD_PHONG} ShadingMode;

#define SWAP(X,Y) X^=Y; Y^=X; X^=Y;

//the Pen is simply an immutable object...
class Pen {
private:
	GLfloat m_red;
	GLfloat m_green;
	GLfloat m_blue;
public:
	Pen(GLfloat r=1, GLfloat g=1, GLfloat b=1):m_red(r),m_green(g),m_blue(b) {}
	inline GLfloat getRed() { return m_red; }
	inline GLfloat getBlue() { return m_blue; }
	inline GLfloat getGreen() { return m_green; }
};

struct VertexData {
	vec4 m_3dCamFrameCoords;
	vec2 m_2dCoords;
	GLfloat m_zValueInNCS;
	vec4 m_normalInCamFrame;
	vec3 m_ambientColor;
	vec3 m_diffuseColor;
	vec3 m_specularColor;

	vec3 m_aggColor;

	VertexData(	const vec4& camFrameCoords, const vec2& w, const GLfloat & zVal, const vec4 & normalCam,
				const vec3& ambientColor=vec3(0,0,0), const vec3& diffuseColor=vec3(0,0,0), const vec3 & specularColor=vec3(0,0,0)):
				m_3dCamFrameCoords(camFrameCoords),m_2dCoords(w),m_zValueInNCS(zVal),m_normalInCamFrame(normalCam),
				m_ambientColor(ambientColor),m_diffuseColor(diffuseColor),m_specularColor(specularColor){}

	vec3 combinedColor() const { return (m_ambientColor + m_diffuseColor + m_specularColor); }
};

using namespace std;
class Renderer
{
	float *m_outBuffer;

	FastArray<float> * m_zbuffer;

	int m_width, m_height;
	Pen m_regularPen;
	Pen m_activePen;
	Pen * m_currentPen;	
	bool flagDrawNormals;
	bool flagDrawVertexNormals;
	bool flagDrawBoundingBox;

	mat4 m_projectionMatrix;
	mat4 m_cameraMatrix;
	
	mat4 m_objectMatrix;
	mat3 m_normalMatrix;

	GLfloat m_normalLength;
	Material * m_currentMaterial;

	vector<LightSource*> m_lights;

	FastArray<int> * m_leftBound;
	FastArray<int> * m_rightBound;
	int m_scanStart;
	int m_scanEnd;

	ShadingMode m_shadingMode;

	vec3 m_flatPolygonColor;

	int m_linesDrawn;

	void TransformToViewport(vec2 & v);
	void CreateBuffers(int width, int height);

	void DrawSingleTriangle(const vec4 & v1,const vec4 & v2,const vec4 & v3);

	//this method will be used to draw things using the various shading models...
	void DrawSingleTriangle(const vec4& v1,  vec4 n1,
							const vec4& v2,  vec4 n2,
							const vec4& v3,  vec4 n3);
	
	void drawVertexNormal(const vec3& v,const vec3& n, GLfloat modelDiameter);

	void calculateColorForVertex(	const vec4& vertexInCamFrame, const vec3& normalInCamFrame,
									vec3 & totalAmbient, vec3 & totalDiffuse, vec3 & totalSpecular);


	vec2 TransformVertex(const vec4 & v);
	vec2 ProjectVertex(const vec4 & v);
	vec4 Transform2NSC(const vec4 & v);
	vec2 Transform2Screen(const vec4 & v);

	void calculateColorAndZVal(	const VertexData& v1, const VertexData& v2, GLfloat ratio, 
								vec3 & colorRes, GLfloat & zValRes);

	void calculateColorAndZVal(	const VertexData& v1, const VertexData& v2, const VertexData& v3,
								GLfloat ratio1, GLfloat ratio2, vec3 & colorRes, GLfloat & zValRes);

	void scanAndFill(const VertexData& v1, const VertexData& v2, const VertexData& v3);

	//////////////////////////////
	// openGL stuff. Don't touch.

	GLuint gScreenTex;
	GLuint gScreenVtc;
	void CreateOpenGLBuffer();
	void InitOpenGLRendering();

	vec3 calculateNormal(const vec4& v1,const vec4& v2,const vec4& v3);
	vec3 calculateTriangleCenter(const vec4& v1,const vec4& v2,const vec4& v3);
	//////////////////////////////

public:
	Renderer();
	Renderer(int width, int height);
	~Renderer(void);
	void Init();
	void DrawLine(const vec2 & p1, const vec2 & p2);
	// why vec3 and not vec4? should we first translate to homogenous coordinates? Well I assume yes...
	void DrawTriangles(const vector<vec3>* vertices, const vector<vec3>* normals=NULL);
	void SetProjection(const mat4& projection);
	void SetObjectMatrices(const mat4& oTransform, const mat3& nTransform);
	void SwapBuffers();
	void ClearColorBuffer();
	void ClearDepthBuffer();
	void SetDemoBuffer();
	void PlotPixel(const int & x, const int & y);
	void ClearOutBuffer() { for (int i=0; i< m_height*m_width*3; i++) m_outBuffer[i] = 0; }
	void DrawPlusAtVertex(const vec4 & v);
	inline void TakeRegularPen() { m_currentPen = &m_regularPen; }
	inline void TakeActivePen() { m_currentPen = &m_activePen; }
	void toggleFaceNormals(){	flagDrawNormals = !flagDrawNormals;	}
	void toggleVertexNormals(){	flagDrawVertexNormals = !flagDrawVertexNormals; }
	void toggleBoundingBox(){ flagDrawBoundingBox = !flagDrawBoundingBox; };
	void drawNormalForTriangle(const vec4& v1,const vec4& v2,const vec4& v3, GLfloat modelDiameter);
	void drawVertexNormals(const vector<vec3> * vertices, const vector<vec3> * normals);//,GLfloat modelDiameter);
	void drawBoundingBox(vec3 boundingBoxMin, vec3 boundingBoxMax);
	void setNormalLength( const GLfloat & nLength) { m_normalLength = nLength; }
	void ReCreateBuffers(int width, int height);
	inline void SetActivePen(const Pen & p) { this->m_activePen = p; }
	inline void SetPassivePen(const Pen & p) {this->m_regularPen = p; }
	//inline void setCOP(const vec4& cop) { m_COP = cop; }
	void DrawLine(const vec2& w1, const vec2& w2, const vec4& n1, const vec4& n2, GLfloat z1, GLfloat z2, const vec3& c1, const vec3& c2);
	void PlotPixel(const int & x, const int & y, GLfloat avgZVal, const vec3 & pixelColor);
	inline void ClearZBuffer() { m_zbuffer->initWithVal(2); }
	inline vec4 applyModelViewTransform(const vec4& v) const {return m_objectMatrix*v; }
	vec4 applyProjectionTransform(const vec4& v) const;
	void printPixelData(int x, int y) const ;
	void DrawLine(const VertexData& vd1, const VertexData& vd2);
};
